package jp.co.nes.awssupport.test.utility;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import jp.co.nes.awssupport.common.bean.BaseBean;
import jp.co.nes.awssupport.common.dao.BeanProcessorEx;

/**
 * Assertに関するHelperクラスを作成する際に使用する抽象クラスです。
 *
 * @param <T> BaseBeanクラスを継承したBeanクラス
 */
public abstract class AbstractAssertHelper<T extends BaseBean> {

    /** Beanクラスのクラスオブジェクト */
    private Class<T> beanClass;

    /** 期待値リスト */
    protected List<T> expectedList = new ArrayList<>();

    /** 結果値リスト */
    protected List<T> actualList = new ArrayList<>();

    /**
     * Beanクラスのクラスオブジェクトを引数に持つコンストラクタです。
     *
     * @param beanClass Beanクラスのクラスオブジェクト
     */
    public AbstractAssertHelper(Class<T> beanClass) {
        this.beanClass = beanClass;
    }

    /**
     * 期待値を追加します。
     *
     * @param bean 期待値
     */
    public void addExpected(T bean) {
        expectedList.add(bean);
    }

    /**
     * 結果値を追加します。
     *
     * @param bean 結果値
     */
    public void addActual(T bean) {
        actualList.add(bean);
    }

    /**
     * 結果値リストを追加します。
     *
     * @param beanList 結果値リスト
     */
    public void addActual(List<T> beanList) {
        actualList.addAll(beanList);
    }

    /**
     * 期待値をソートします。
     *
     * @param comparator ソートに使用するコンパレータ
     */
    public void sortExpected(Comparator<T> comparator) {
        expectedList.sort(comparator);
    }

    /**
     * {@link AbstractAssertHelper#getSelectCountQuery()}で返却されたクエリーを実行し、実行結果の件数を返却します。
     *
     * @return 件数
     */
    public long count() {
        try {
            Connection connection = JUnitDbConnUtil.getConn();
            QueryRunner runner = new QueryRunner();
            Long count = runner.query(connection, getSelectCountQuery(), new ScalarHandler<Long>("Count"),
                    new Object[0]);

            long result = 0;
            if (count != null) {
                if (count > 0) {
                    result = count;
                }
            }
            return result;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * アサーションです。<br>
     * {@link AbstractAssertHelper#getSelectBeanQuery()}で返却されたクエリーを、
     * {@link AbstractAssertHelper#toConditionList(BaseBean)}で返却された条件をつけて実行し、
     * その結果を期待値リストの内容と比較します。
     */
    public void assertTestWithSelect() {
        try {
            Connection connection = JUnitDbConnUtil.getConn();
            QueryRunner runner = new QueryRunner();

            int cnt = 0;
            for (T expectedBean : expectedList) {
                List<Object> paramList = toConditionList(expectedBean);
                Object[] params = paramList.toArray();

                List<T> actualList = runner.query(connection, getSelectBeanQuery(),
                        new BeanListHandler<>(beanClass, new BasicRowProcessor(new BeanProcessorEx())),
                        params);
                assertThat("actual not exist(expectedCount=" + cnt + ")", actualList, notNullValue());
                assertThat("actual size wrong(expectedCount=" + cnt + ")", actualList.size(), is(1));
                assertBean("(expectedCount=" + cnt + ")", actualList.get(0), expectedBean);
                cnt++;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * アサーションです。<br>
     * 結果値リストと期待値リストを比較します。<br>
     * 結果値リストが期待値リストどおりにソートされている場合に使用します。
     */
    public void assertTestForSortActual() {
        assertThat(actualList.size(), is(expectedList.size()));
        for (int i = 0; i < expectedList.size(); i++) {
            assertBean("(expectedCount=" + i + ")", actualList.get(i), expectedList.get(i));
        }
    }

    /**
     * アサーションです。<br>
     * 結果値リストと期待値リストを比較します。<br>
     * 結果値リストが期待値リストどおりにソートされていない場合に使用します。
     */
    public void assertTestForNotSortActual() {
        assertThat(actualList.size(), is(expectedList.size()));

        int cnt = 0;
        for (T expectedBean : expectedList) {
            List<T> filterList = actualList.stream().filter(bean -> {
                return isMatch(bean, expectedBean);
            }).collect(Collectors.toList());
            if (filterList.size() != 1) {
                fail("test fail");
            }
            T actualBean = filterList.get(0);
            assertBean("(expectedCount=" + cnt + ")", actualBean, expectedBean);
            cnt++;
        }
    }

    /**
     * アサーションです。
     *
     * @param suffixReason reasonのsuffix
     * @param actualBean 結果値
     * @param expectedBean 期待値
     */
    public void assertBean(String suffixReason, T actualBean, T expectedBean) {
        AssertUtil.assertBean(suffixReason, actualBean, expectedBean);
    }

    /**
     * アサーションです。
     *
     * @param actualBean 結果値
     * @param expectedBean 期待値
     */
    public void assertBean(T actualBean, T expectedBean) {
        AssertUtil.assertBean(actualBean, expectedBean);
    }

    /**
     * 件数を取得するSelect文を返却します。
     *
     * @return Select文
     */
    protected abstract String getSelectCountQuery();

    /**
     * データを取得するSelect文を返却します。
     *
     * @return Select文
     */
    protected abstract String getSelectBeanQuery();

    /**
     * 検索条件リストを返却します。
     *
     * @param bean 検索条件に設定する値が含まれるBeanクラス
     * @return 検索条件リスト
     */
    protected abstract List<Object> toConditionList(T bean);

    /**
     * 結果値と期待値が同じかどうかを返却します。
     *
     * @param actualBean 結果値
     * @param expectedBean 期待値
     * @return true: 同じである, false: 同じでない。
     */
    protected abstract boolean isMatch(T actualBean, T expectedBean);
}
